package org.elsfs.cloud.common.mybatis.ext.db;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.BeanUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.elsfs.cloud.common.mybatis.ext.sql.SqlParameterManager;
import org.elsfs.cloud.common.mybatis.ext.sql.support.BatchUtils;
import org.elsfs.cloud.common.util.lang.StringUtils;
import org.mybatis.spring.SqlSessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.ClassUtils;

import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * VEF 通用SQL执行器
 *
 */
@RequiredArgsConstructor
public class VefGenericSqlExecutor implements GenericSqlExecutor {
    /**
     * SQL执行批量数量
     */
    public static final int SQL_EXECUTION_BATCH_SIZE = 500;

    /**
     * SQL会话工厂
     */
    private final SqlSessionFactory sqlSessionFactory;
    /**
     * 通用SQL执行Mapper
     */
    private final GenericSqlExecutorMapper sqlExecutorMapper;

    /**
     * 查询单条数据
     *
     * @param sql   SQL
     * @param clazz 结果类
     * @return 结果
     */
    @Override
    public <T> T selectOne(String sql, Class<T> clazz) {
        return this.selectOne(sql, null, clazz);
    }

    /**
     * 构建SQL参数
     *
     * @param sql        SQL
     * @param parameters SQL参数
     * @return 最终SQL参数
     */
    private Map<String, Object> buildSqlParameters(String sql, Map<String, Object> parameters) {
        Map<String, Object> parameterMap = new HashMap<>(CollectionUtils.size(parameters));
        parameterMap.put(GenericSqlExecutorMapper.SQL_PARAMETER_NAME, sql);
        if (parameters != null) {
            parameterMap.putAll(parameters);
        }
        return parameterMap;
    }

    /**
     * 查询单条数据
     *
     * @param sql        SQL
     * @param parameters SQL参数
     * @param clazz      结果类
     * @return 结果
     */
    @Override

    public <T> T selectOne(String sql, Map<String, Object> parameters, Class<T> clazz) {
        Map<String, Object> resultMap = this.sqlExecutorMapper.selectOne(
                this.buildSqlParameters(sql, parameters)
        );

        if (MapUtils.isEmpty(resultMap)) {
            return null;
        }

        if (ClassUtils.isSimpleValueType(clazz)) {
            return BeanUtils.mapToBean(resultMap, clazz);
        }

        return BeanUtils.mapToBean(resultMap, clazz);
    }

    /**
     * 查询列表数据
     *
     * @param sql   SQL
     * @param clazz 结果类
     * @return 结果
     */
    @Override

    public <T> List<T> selectList(String sql, Class<T> clazz) {
        return this.selectList(sql, null, clazz);
    }

    /**
     * 查询列表数据
     *
     * @param sql        SQL
     * @param parameters SQL参数
     * @param clazz      结果类
     * @return 结果
     */
    @Override

    public <T> List<T> selectList(String sql, Map<String, Object> parameters, Class<T> clazz) {
        List<Map<String, Object>> resultMaps = this.sqlExecutorMapper.selectList(
                this.buildSqlParameters(sql, parameters)
        );

        if (CollectionUtils.isEmpty(resultMaps)) {
            return Collections.emptyList();
        }

        if (ClassUtils.isSimpleValueType(clazz)) {
            return BeanUtils.mapsToBeans(resultMaps, clazz);
        }

        return resultMaps.stream()
                .map(resultMap -> BeanUtils.mapToBean(resultMap, clazz))
                .toList();
    }

    /**
     * 查询分页数据
     *
     * @param sql   SQL
     * @param page  分页对象
     * @param clazz 结果类
     * @return 结果
     */
    @Override

    public <T> IPage<T> selectPage(String sql, IPage<T> page, Class<T> clazz) {
        return this.selectPage(sql, page, null, clazz);
    }

    /**
     * 查询分页数据
     *
     * @param sql        SQL
     * @param page       分页对象
     * @param parameters SQL参数
     * @param clazz      结果类
     * @return 结果
     */
    @Override

    public <T> IPage<T> selectPage(String sql, IPage<T> page, Map<String, Object> parameters, Class<T> clazz) {
        if (MapUtils.isNotEmpty(parameters)) {
            for (String key : parameters.keySet()) {
                sql = sql.replace(
                        SqlParameterManager.buildParameterPlaceholder(key),
                        SqlParameterManager.buildParameterPlaceholder(
                                GenericSqlExecutorMapper.PAGE_SQL_PARAMETER_NAME_PREFIX + key
                        )
                );
            }
        }

        IPage<Map<String, Object>> resultPage = this.sqlExecutorMapper.selectPage(
                page,
                this.buildSqlParameters(sql, parameters)
        );
        Page<T> finalPage = Page.of(
                resultPage.getCurrent(),
                resultPage.getSize(),
                resultPage.getTotal()
        );

        if (CollectionUtils.isEmpty(resultPage.getRecords())) {
            return finalPage;
        }

        finalPage.setRecords(
                resultPage.getRecords().stream()
                        .map(resultMap -> BeanUtils.mapToBean(resultMap, clazz))
                        .toList()
        );
        return finalPage;
    }

    /**
     * 查询列表数据，并根据其中一个字段转为Map
     *
     * @param sql         SQL
     * @param mapKeyField Map键对应的字段名称
     * @param clazz       结果类
     * @return 结果
     */
    @Override

    public <T> Map<String, T> selectMap(String sql, String mapKeyField, Class<T> clazz) {
        return this.selectMap(sql, null, mapKeyField, clazz);
    }

    /**
     * 查询列表数据，并根据其中一个字段转为Map
     *
     * @param sql         SQL
     * @param parameters  SQL参数
     * @param mapKeyField Map键对应的字段名称
     * @param clazz       结果类
     * @return 结果
     */
    @Override

    public <T> Map<String, T> selectMap(String sql, Map<String, Object> parameters, String mapKeyField, Class<T> clazz) {
        SqlSessionHolder sqlSessionHolder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(this.sqlSessionFactory);
        SqlSession sqlSession;
        boolean needClose = true;
        if (sqlSessionHolder != null) {
            sqlSession = sqlSessionHolder.getSqlSession();
            needClose = false;
        } else {
            sqlSession = this.sqlSessionFactory.openSession(ExecutorType.REUSE, true);
        }

        try {
            Map<Object, Map<String, Object>> resultMap = sqlSession.selectMap(
                    GenericSqlExecutorMapper.class.getName() + "." + "selectList",
                    this.buildSqlParameters(sql, parameters),
                    mapKeyField
            );
            return resultMap.entrySet().stream()
                    .collect(
                            Collectors.toMap(
                                    entry -> {
                                        if (entry.getKey() instanceof String key) {
                                            return key;
                                        }

                                        return StringUtils.str(entry.getKey(), StandardCharsets.UTF_8);
                                    },
                                    entry -> BeanUtils.mapToBean(entry.getValue(), clazz)
                            )
                    );
        } finally {
            if (needClose) {
                sqlSession.close();
            }
        }
    }

    /**
     * 批量操作处理
     *
     * @param operator 批量操作消费器
     */
    private void batchOperate(Consumer<SqlSession> operator) {
        SqlSessionHolder sqlSessionHolder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(this.sqlSessionFactory);
        boolean transactionActive = TransactionSynchronizationManager.isSynchronizationActive();
        if (sqlSessionHolder != null) {
            SqlSession sqlSession = sqlSessionHolder.getSqlSession();
            sqlSession.commit(!transactionActive);
        }

        SqlSession sqlSession = this.sqlSessionFactory.openSession(ExecutorType.BATCH, false);
        try {
            operator.accept(sqlSession);
            sqlSession.commit(!transactionActive);
        } catch (Throwable e) {
            sqlSession.rollback();
            throw e;
        } finally {
            sqlSession.close();
        }
    }

    /**
     * 插入数据
     *
     * @param sql SQL
     * @return 影响行数
     */
    @Override
    public int insert(String sql) {
        return this.insert(sql, null);
    }

    /**
     * 插入数据
     *
     * @param sql        SQL
     * @param parameters SQL参数
     * @return 影响行数
     */
    @Override
    public int insert(String sql, Map<String, Object> parameters) {
        return this.sqlExecutorMapper.insert(
                this.buildSqlParameters(sql, parameters)
        );
    }

    /**
     * 批量插入
     *
     * @param sql           SQL
     * @param parameterMaps SQL参数集合
     */
    @Override
    public void batchInsert(String sql, Collection<Map<String, Object>> parameterMaps) {
        this.batchOperate(
                sqlSession -> BatchUtils.batchExecute(
                        parameterMaps,
                        SQL_EXECUTION_BATCH_SIZE,
                        parameterMap -> sqlSession.insert(
                                GenericSqlExecutorMapper.class.getName() + "." + "insert",
                                this.buildSqlParameters(sql, parameterMap)
                        ),
                        i -> sqlSession.flushStatements()
                )
        );
    }

    /**
     * 更新数据
     *
     * @param sql SQL
     * @return 影响行数
     */
    @Override
    public int update(String sql) {
        return this.update(sql, null);
    }

    /**
     * 更新数据
     *
     * @param sql        SQL
     * @param parameters SQL参数
     * @return 影响行数
     */
    @Override
    public int update(String sql, Map<String, Object> parameters) {
        return this.sqlExecutorMapper.update(
                this.buildSqlParameters(sql, parameters)
        );
    }

    /**
     * 批量更新
     *
     * @param sql           SQL
     * @param parameterMaps SQL参数集合
     */
    @Override
    public void batchUpdate(String sql, Collection<Map<String, Object>> parameterMaps) {
        this.batchOperate(
                sqlSession -> BatchUtils.batchExecute(
                        parameterMaps,
                        SQL_EXECUTION_BATCH_SIZE,
                        parameterMap -> sqlSession.update(
                                GenericSqlExecutorMapper.class.getName() + "." + "update",
                                this.buildSqlParameters(sql, parameterMap)
                        ),
                        i -> sqlSession.flushStatements()
                )
        );
    }

    /**
     * 删除数据
     *
     * @param sql SQL
     * @return 影响行数
     */
    @Override
    public int delete(String sql) {
        return this.delete(sql, null);
    }

    /**
     * 删除数据
     *
     * @param sql        SQL
     * @param parameters SQL参数
     * @return 影响行数
     */
    @Override
    public int delete(String sql, Map<String, Object> parameters) {
        return this.sqlExecutorMapper.delete(
                this.buildSqlParameters(sql, parameters)
        );
    }

    /**
     * 批量删除
     *
     * @param sql           SQL
     * @param parameterMaps SQL参数集合
     */
    @Override
    public void batchDelete(String sql, Collection<Map<String, Object>> parameterMaps) {
        this.batchOperate(
                sqlSession -> BatchUtils.batchExecute(
                        parameterMaps,
                        SQL_EXECUTION_BATCH_SIZE,
                        parameterMap -> sqlSession.delete(
                                GenericSqlExecutorMapper.class.getName() + "." + "delete",
                                this.buildSqlParameters(sql, parameterMap)
                        ),
                        i -> sqlSession.flushStatements()
                )
        );
    }
}
